.NET: Add MapAGUI overload that resolves the agent per request via a factory - #6251
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new MapAGUI overload that creates an AIAgent per request via a factory delegate, and refactors the shared request-handling logic into a helper to reduce duplication.
Changes:
- Added
MapAGUIoverload acceptingFunc<IServiceProvider, string, AIAgent>for per-request agent resolution. - Refactored endpoint handler body into
HandleRunAsyncand reused it from existing overload(s). - Added unit tests for the new overload (mapping, deferral behavior, and null delegate guard).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs | Adds new unit tests targeting the new factory-delegate MapAGUI overload and its mapping-time behavior. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/AGUIEndpointRouteBuilderExtensions.cs | Introduces the per-request factory-delegate overload and extracts shared request processing into HandleRunAsync. |
|
@microsoft-github-policy-service agree |
|
This would be helpful! I had to add |
| { | ||
| return Results.BadRequest(); | ||
| } | ||
| var aiAgent = createAgentDelegate(context.RequestServices, agentName) |
There was a problem hiding this comment.
What happens when the factory creates a new ChatClientAgent per request without explicitly setting ChatClientAgentOptions.Id? AIAgent.Id defaults to a new GUID per instance, and the built-in session stores key by (agent.Id, conversationId), so the same AG-UI ThreadId will miss the previously saved session on the next request. Could this overload preserve a stable hosted identity for agentName, or fail fast/document an explicit stable-ID requirement before session persistence silently resets every turn?
…via a factory Adds a per-request factory-delegate overload of MapAGUIServer for agents that must be built with request-scoped state (per-request auth, scoped tool/MCP sessions, conversation-scoped config) rather than a single instance captured at startup. Preserves stable session identity: AgentSessionStore keys persisted sessions by (agent.Id, conversationId), and AIAgent.Id defaults to a per-instance GUID. A per-request factory would therefore compute a different session key every turn and never find the previously saved session -- silently resetting persistence. The overload wraps the per-request agent in a StableIdentityAIAgent whose Id is the logical agentName, so the session key stays constant across requests, matching the startup-capture overloads. Both overloads now share BuildHostAgent (isolation-store wiring) and HandleRunAsync. Tests: factory mapping/deferral/null-arg unit tests; a stable-id assertion; a two-turn persistence round-trip through the real InMemoryAgentSessionStore and a negative control proving raw per-request instances lose the session; plus integration tests asserting per-request invocation and the null-factory failure.
87d41db to
d29e9af
Compare
|
Evan Mattson (@moonbox3) Great catch — confirmed and fixed. AIHostAgent keys the session store by InnerAgent.Id and AIAgent.Id defaults per-instance, so a per-request factory reset persistence every turn. The overload now wraps the per-request agent in a StableIdentityAIAgent (Id = agentName), keeping the session key stable across requests — matching the startup-capture overloads. While here I also rebased onto current main and retargeted to MapAGUIServer (the PR had gone conflict-dirty against the AG-UI SDK externalization). Added stable-id + two-turn persistence round-trip tests (with a negative control) and integration coverage; both suites green locally. Happy to switch to a fail-fast on a non-stable id instead of the implicit facade if you'd prefer. |
Summary
Adds a
MapAGUIoverload that resolves the agent per request via a factory delegate, instead of capturing a singleAIAgentat startup from the root service provider.Motivation
The existing
MapAGUIoverloads resolve one keyedAIAgentonce at map time (endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(name)). Hosts that must build the agent per request — for per-request authentication, scoped tool/MCP sessions, or conversation-scoped configuration — have no supported entry point and must work around it (e.g. a singleton facadeAIAgentthat re-resolves a real agent on each run).This overload invokes the factory once per request with the request's
IServiceProvider(HttpContext.RequestServices) and the agent name, so request-scoped services resolve correctly. It mirrors the existingAddAIAgent(name, Func<IServiceProvider, string, AIAgent>)delegate shape.Details
AgentSessionStorelookup and theThreadIdtrust model are unchanged; the store is resolved per request fromHttpContext.RequestServicesusingagentNameas the key.HandleRunAsyncreused by both the instance and factory overloads (no behavior change to the existing path).endpoints,agentName, andcreateAgentDelegate; the factory returning null throws a clearInvalidOperationException.Tests
Adds unit tests: the overload maps an endpoint; it defers resolution (factory not invoked and no keyed
AIAgentresolved at map time, unlike the startup-capture overloads); and a null-factory guard. All 38 tests inMicrosoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTestspass on net8.0/net9.0/net10.0.Closes #2988
Related to #5209